package org.budo.dubbo.protocol.http.sign;

import org.budo.dubbo.protocol.http.Consts;
import org.budo.dubbo.protocol.http.exception.BudoRuntimeException;
import org.budo.dubbo.protocol.http.exception.remoting.SignCheckException;
import org.budo.dubbo.protocol.http.exception.remoting.SignCheckException.Fail;
import org.budo.dubbo.protocol.http.invocation.BudoDubboRpcInvocation;
import org.budo.support.lang.util.StringUtil;
import org.budo.time.Time;
import org.springframework.util.DigestUtils;

import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.rpc.Invocation;

/**
 * @author lmw
 */
public abstract class AbstractSignCheckService implements SignCheckService {
    /**
     * 发起调用和被调用时均会使用到
     */
    public abstract String getAppSecret(String appKey);

    /**
     * 发起调用时使用到
     */
    public abstract String getAppKey(Invocation invocation);

    @Override
    public SignEntity sign(Invocation invocation) {
        String consumerSideAppKey = this.getAppKey(invocation);
        String consumerSideAppSecret = this.getAppSecret(consumerSideAppKey);

        String requestBody = ((BudoDubboRpcInvocation) invocation).getRequestBody();
        String consumerSideTime = Time.now().toMilliSeconds().toString();

        String sign_v2 = this.sign_v2(requestBody, consumerSideTime, consumerSideAppSecret);
        return new SignEntity(sign_v2, consumerSideAppKey, consumerSideTime); // 客户端暂用旧版加密，待服务端用新加密后，全改为新加密，2018年3月29日
    }

    @Override
    public Boolean signCheck(Invocation invocation) {
        String consumerSideAppKey = this.readSignCheckAttachment(invocation, Consts.HeaderKey.AppKey, Fail.APP_KEY_EMPTY);
        String consumerSideSign = this.readSignCheckAttachment(invocation, Consts.HeaderKey.Sign, Fail.SIGN_EMPTY);

        String providerSideAppSecret = this.getAppSecret(consumerSideAppKey);

        String consumerSideTime = this.readSignCheckAttachment(invocation, Consts.HeaderKey.Time, Fail.TIME_EMPTY); // 顺序靠后，优化错误提示信息
        String requestBody = ((BudoDubboRpcInvocation) invocation).getRequestBody();

        // 最新版本sign‘验证
        String providerSideSign_v2 = this.sign_v2(requestBody, consumerSideTime, providerSideAppSecret);
        if (StringUtil.equalsIgnoreCase(providerSideSign_v2, consumerSideSign)) {
            return true;
        }

        return false;
    }

    protected String readSignCheckAttachment(Invocation invocation, String key, Fail fail) {
        String value = invocation.getAttachment(key);

        if (StringUtils.isEmpty(value)) {
            String message = "#88 " + key + " in attachment required, invocation=" + invocation;
            throw new SignCheckException(message, fail);
        }

        return value;
    }

    /**
     * 按照key的字母顺序来
     */
    protected String sign_v2(String requestBody, String time, String appSecret) {
        String toSignString = "appSecret=" + appSecret + "&requestBody=" + requestBody + "&time=" + time;
        return md5(toSignString);
    }

    protected String md5(String toSignString) {
        byte[] toSignBytes = toSignString.getBytes();

        try {
            return DigestUtils.md5DigestAsHex(toSignBytes);
        } catch (Throwable e) {
            String message = "#172 md5 error, toSignString=" + toSignString + ", e=" + e;
            throw new BudoRuntimeException(message, e);
        }
    }
}